#include <misc/offset.h>
#include <base/BLog.h>

#include "ServerInterface.h"

#include <generated/blog_channel_ServerInterface.h>

#define RETRY_TIME 5000

#define STATE_CONNECTING 1
#define STATE_CONNECTED 2
#define STATE_WAITING 3

static void try_init_client (ServerInterface *o);
static void timer_handler (ServerInterface *o);
static void client_handler_error (ServerInterface *o);
static void client_handler_connected (ServerInterface *o);
static void up_job_handler (ServerUser *o);

static void try_init_client (ServerInterface *o)
{
    BLog(BLOG_INFO, "connecting");
    
    if (!NCDRequestClient_Init(&o->client, o->addr, o->reactor, o, (NCDRequestClient_handler_error)client_handler_error, (NCDRequestClient_handler_connected)client_handler_connected)) {
        BLog(BLOG_ERROR, "NCDRequestClient_Init failed");
        goto fail;
    }
    
    o->state = STATE_CONNECTING;
    return;
    
fail:
    BReactor_SetTimer(o->reactor, &o->timer);
    o->state = STATE_WAITING;
}

static void timer_handler (ServerInterface *o)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(o->state == STATE_WAITING)
    
    try_init_client(o);
}

static void client_handler_error (ServerInterface *o)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(o->state == STATE_CONNECTING || o->state == STATE_CONNECTED)
    
    BLog(BLOG_ERROR, "connection error");
    
    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->users_list); ln; ln = LinkedList1Node_Next(ln)) {
        ServerUser *u = UPPER_OBJECT(ln, ServerUser, users_list_node);
        ASSERT(u->iface == o)
        if (u->reported_up) {
            u->reported_up = 0;
            u->func_down(u->user);
        }
    }
    
    NCDRequestClient_Free(&o->client);
    
    BReactor_SetTimer(o->reactor, &o->timer);
    o->state = STATE_WAITING;
}

static void client_handler_connected (ServerInterface *o)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(o->state == STATE_CONNECTING)
    
    BLog(BLOG_INFO, "connected");
    
    o->state = STATE_CONNECTED;
    
    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->users_list); ln; ln = LinkedList1Node_Next(ln)) {
        ServerUser *u = UPPER_OBJECT(ln, ServerUser, users_list_node);
        ASSERT(u->iface == o)
        ASSERT(!u->reported_up)
        BPending_Set(&u->up_job);
    }
}

static void up_job_handler (ServerUser *o)
{
    DebugObject_Access(&o->d_obj);
    
    if (!o->reported_up && o->iface->state == STATE_CONNECTED) {
        o->reported_up = 1;
        o->handler_up(o->user, &o->iface->client);
        return;
    }
}

int ServerInterface_Init (ServerInterface *o, struct NCDRequestClient_addr addr, BReactor *reactor)
{
    o->addr = addr;
    o->reactor = reactor;
    
    LinkedList1_Init(&o->users_list);
    
    BTimer_Init(&o->timer, RETRY_TIME, (BTimer_handler)timer_handler, o);
    
    try_init_client(o);
    
    DebugObject_Init(&o->d_obj);
    return 1;
}

void ServerInterface_Free (ServerInterface *o)
{
    DebugObject_Free(&o->d_obj);
    ASSERT(LinkedList1_IsEmpty(&o->users_list))
    
    if (o->state != STATE_WAITING) {
        NCDRequestClient_Free(&o->client);
    }
    
    BReactor_RemoveTimer(o->reactor, &o->timer);
}

void ServerUser_Init (ServerUser *o, ServerInterface *iface, void *user, ServerUser_handler_up handler_up, ServerUser_func_down func_down)
{
    DebugObject_Access(&iface->d_obj);
    
    o->iface = iface;
    o->user = user;
    o->handler_up = handler_up;
    o->func_down = func_down;
    
    LinkedList1_Append(&iface->users_list, &o->users_list_node);
    
    BPending_Init(&o->up_job, BReactor_PendingGroup(iface->reactor), (BPending_handler)up_job_handler, o);
    BPending_Set(&o->up_job);
    
    o->reported_up = 0;
    
    DebugObject_Init(&o->d_obj);
}

void ServerUser_Free (ServerUser *o)
{
    DebugObject_Free(&o->d_obj);
    
    BPending_Free(&o->up_job);
    
    LinkedList1_Remove(&o->iface->users_list, &o->users_list_node);
}
